// @Name    : zx_alert_text_field
// @author  : simon
// @date    : 2021/10/17 10:59
// @desc : 2024.06.05 解决释放引用值textEditingController，外部再释放会崩溃的问题。
// 重大改动：设置显示区域最大行数
// textField无法获取到textEditingController对象，无法获取text值？
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
export 'package:flutter/services.dart';
export 'cai_text_number_limit_formatter.dart';

/// 简介：自定义material风格的TextField 输入框组件。textField外部包裹一个Container，增加清除按钮。
/// 增加始终显示的前缀、后缀的设置。前置是否包含ObscureText *。
/// 系统的TextField：
/// 1.遇到需要一直展示前缀提示文本的，则需要自定义Container{Text组件+无边框TextField},系统的不满足。
// 系统的TextField/TextFormField的prefix,prefixText属性默认都只是预加载，
// 默认情况下不展示内容，只占用空间,触发输入焦点的时候才会展示。prefixIcon：默认就展示。后缀同理。
/// 2. 在Collapsed模式中，InputDecoration的labelText，helperText，errorText，prefix，suffix,counter等都不支持；
/*
注意：如果InputDecorator包含在Row中，TextField必须使用Expanded小部件或SizedBox来约束。
1)BoxConstraints forces an infinite width.
2)An InputDecorator, which is typically created by a TextField, cannot have an unbounded width.
通常由TextField创建的InputDecorator不能具有无界宽度。
当父小部件没有提供有限宽度约束时，就会发生这种情况。
例如，如果InputDecorator包含在Row中，那么它的宽度必须受到约束。
可使用Expanded小部件或SizedBox来约束InputDecorator或包含它的TextField的宽度。
 */
/*
例如：
          Padding(
            padding: EdgeInsets.only(top: 30.w),
            child: ZXCustomTextField(
              height: 88.w,
              hintText: '请输入手机号',
              inputFormatters: [LengthLimitingTextInputFormatter(11)],
              controller: _phoneController,
              keyboardType:TextInputType.number,
            ),
          ),
          Padding(
            padding: EdgeInsets.only(top: 30.w),
            child: ZXCustomTextField(
                height: 88.w,
                hintText: '请输入密码',
                obscureText: true,
                controller: _serController,
                keyboardType: TextInputType.url),
          ),
 */

const double kTextFieldFontSize = 15;

class ZXCustomTextField extends StatefulWidget {
  /// 组件宽度,默认null
  final double? width;

  /// 设置decoration外观
  /// 组件高度,默认32
  final double? height;

  /// 组件的内边距。默认const EdgeInsets.symmetric(horizontal: 16, vertical: 0);
  final EdgeInsetsGeometry? containerPadding;

  /// 背景颜色，默认Color(0xFFF2F1F6)
  final Color? containerBackgroundColor;

  /// 输入框边框；内部原理是包裹的container边框。默认BorderRadius.circular(5)
  final BorderRadius? borderRadius;

  /// 设置外边到child和decoration的外间距，默认zero。
  final EdgeInsetsGeometry? containerMargin;

  /// 设置textField距离prefix的间距，默认16.w，在没有prefix的时候，是0。在没有prefix的时候，
  /// 视觉上是光标距离边框的距离。
  final double? gapPrefixSpace;

  /// 自定义textField前置文本；内部原理是textField之前放置text组件；默认null。
  final String? prefixText;
  final TextStyle? prefixStyle;

  /// 自定义container组件前置widget。
  /// 优先级：prefix>prefixIcon>prefixText;如果设置了prefix，则prefixText和prefixIcon
  /// 不起作用；
  /// 如果设置了prefixIcon，则prefixText不起作用。
  final Widget? prefix;

  /// 前置是否包含ObscureText *;
  final bool prefixContainObscureText;

  /// 设置textField的前缀Icon，textField可编辑部分之前的图标。
  final Widget? prefixIcon;
  final Color? prefixIconColor;
  final Widget? suffix;

  /// 设置容器的触发焦点的border。默认Border.all(color: 主题色, width: 1)
  final Border? focusedBorder;

  /// 设置容器的正常border(设置BoxDecoration的border)，默认null。Border->BoxBorder->ShapeBorder;
  /// 系统TextField的是设置InputDecoration（InputBorder->ShapeBorder）
  /// 例如：Border.all(width: 1),
  final BoxBorder? border;

  /// 设置textField占位符字符串，默认"请点击输入"。
  final String? hintText;

  /// 设置占位符样式，默认TextStyle(fontSize: 14 * lcdX,color: const Color(0xFFCCCCCC),)
  final TextStyle? hintStyle;

  /// 设置用于控制正在被编辑的文本的控制器；如果是null，会自动创建一个TextEditingController；
  /// 也可以配置表单默认显示的内容。
  /// 官方文档：If null, this widget will create its own [TextEditingController].
  final TextEditingController? controller;

  /// 设置此小部件的键盘焦点。可以设置默认就请求触发焦点弹出键盘。
  final FocusNode? focusNode;

  /// 键盘类型。默认TextInputType.text。例如：TextInputType.number:没有小数点的数字键盘。
  /// 有小数点的数字键盘：const TextInputType.numberWithOptions(decimal: true)
  final TextInputType? keyboardType;

  /// 用于设定键盘上action按钮的类型，即键盘上右下角按钮的行为。当设置为 TextInputAction.done 时，按下完成键会自动提交表单并隐藏键盘。这适用于需要用户输入完信息后立即提交的场景，比如登录界面。（一般位于右下角，默认是TextInputAction.done完成）【不用】
  final TextInputAction? textInputAction;

  /// 设置被编辑文本的样式，默认TextStyle(fontSize: 14 * lcdX,color: const Color(0xFF333333),)
  final TextStyle? style;

  /// 设置是否自动焦点，是否自动变成第一响应者。默认false。
  final bool autofocus;

  /// 是否以密码文本形式展示
  final bool obscureText;

  ///设置显示区域最大行数，默认1。最多几行，设置此参数可以把单行文本框改为多行文本框，
  ///如果expands=true，textField高度也会自动适应放大。
  ///如果设置大于1： 1）则隐藏后缀widget和清除按钮,密码眼睛按钮；2）设置不同的ContainerPadding；
  ///3）让自适应高度。
  final int? maxLines;

  //1）如果设置，字符计数器将显示在字段下面，显示已输入的字符数量。如果设置为大于0的数字，它还将显示允许的最大数字。如果设置为[TextField.noMaxLength]则只显示当前字符数。
  // 2）该值必须为null或[TextField.noMaxLength]，或大于0。如果为空(默认值)，则可以输入的字符数没有限制。如果设置为[TextField.noMaxLength]，则不强制限制，但仍会显示输入的字符数。
  final int? maxLength;

  /// 是否只读，默认false。如果只读，则不能变成第一响应者输入。
  final bool readOnly;

  /// 点击事件
  /// TextField自带有点击事件：final GestureTapCallback? onTap;
  final GestureTapCallback? onTap;

  /// 点击文本框事件回调TextEditingController,方便外部设置修改值。用setState重新构建controller是无法修改值的。
  final void Function(TextEditingController controller)? onTapBlockController;

  /// 文本框改变的时候触发的事件；输入文本发生变化时的回调。
  final ValueChanged<String>? onChanged;

  /// 操作完成按钮提交事件。
  // 从TextField读取值的一种常见方法是使用[onSubmitted]回调。当用户完成当前编辑时，
  // 这个回调应用于textField的当前值。对于大多数应用程序，[onSubmitted]回调将足以对用户输入做出反应。
  final ValueChanged<String>? onSubmitted;

  ///编辑完成。自己根据系统的函数做二次处理，让点击完成按钮或者点击屏幕失去焦点的时候回调这个函数。
  ///注意系统的回调函数：1）默认会关闭键盘，如果自己实现它的这个回调，则需要自己关闭键盘；
  ///2）点击屏幕关闭第一响应者的不会回调这个函数；需要二次处理。
  // 用来做检查提示toast。
  final VoidCallback? onCustomEditingComplete;

  /// 输入限制。可以设置数字时：小数点前/小数点后：[CaiTextNumberLimitFormatter(11, 0)]。
  /// 文字时：[LengthLimitingTextInputFormatter(11)]
  final List<TextInputFormatter>? inputFormatters;

  /// 设置是否展示清除按钮,默认true。如果true，则动态展示清除按钮。
  final bool showCleanButton;

  /// 设置是否展示密码眼睛切换按钮
  final bool showObscureTextEye;

  ZXCustomTextField({
    super.key,
    this.width,
    this.height,
    this.containerPadding,
    this.containerBackgroundColor,
    this.borderRadius,
    this.containerMargin,
    this.prefixText,
    this.prefixStyle,
    this.gapPrefixSpace,
    this.prefix,
    this.prefixContainObscureText = false,
    this.suffix,
    this.prefixIcon,
    this.prefixIconColor,
    this.focusedBorder,
    this.border,
    this.hintText,
    this.hintStyle,
    this.controller,
    this.focusNode,
    this.keyboardType,
    this.textInputAction,
    this.style,
    this.autofocus = false,
    this.obscureText = false,
    this.maxLines = 1,
    this.maxLength,
    this.readOnly = false,
    this.onChanged,
    this.onSubmitted,
    this.onCustomEditingComplete,
    this.onTap,
    this.onTapBlockController,
    this.inputFormatters,
    this.showCleanButton = true,
    this.showObscureTextEye = false,
  }) {
    if (kDebugMode) {
      print('');
    }
  }
  @override
  State<ZXCustomTextField> createState() => _ZXCustomTextFieldState();
}

class _ZXCustomTextFieldState extends State<ZXCustomTextField>
    with SingleTickerProviderStateMixin {
  ///动态展示清除按钮
  bool _isShowCleanButton = false;
  late bool _isShowObscureText = widget.obscureText;
  late final FocusNode _focusNode = widget.focusNode ?? FocusNode(debugLabel: '输入框');
  bool _focused = false;

  late final double _height = widget.height ?? (20 + 6 * 2);

  late final double _iconPaddingSize = (_height - 20) / 2 > 6 ? 6 : (_height - 20) / 2;
  late final TextEditingController _controller = widget.controller ?? TextEditingController();

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _focusNode.addListener(_handleFocusChange);
  }

  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    super.didChangeDependencies();
  }

  @override
  void activate() {
    // TODO: implement activate
    super.activate();
  }

  @override
  void didUpdateWidget(covariant ZXCustomTextField oldWidget) {
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);
  }

  @override
  void deactivate() {
    // TODO: implement deactivate
    super.deactivate();
  }

  @override
  void dispose() {
    // TODO: implement dispose
    _focusNode.removeListener(_handleFocusChange);

    ///这里释放了，外部的focusNode就不需要释放了
    _focusNode.dispose();

    ///如果引用外部的，则不需要释放。
    if (widget.controller == null) {
      _controller.dispose();
    }
    super.dispose();
  }

  void _handleFocusChange() {
    if (_focusNode.hasFocus != _focused) {
      ///失去焦点后，回调编辑完成。
      if (_focusNode.hasFocus == false) {
        if (widget.onCustomEditingComplete != null) {
          widget.onCustomEditingComplete!();
        }
      }
      setState(() {
        _focused = _focusNode.hasFocus;
        //不是只读的，showCleanButton=true的时候，要显示clean按钮
        if (widget.showCleanButton && widget.readOnly == false) {
          _isShowCleanButton = _focused;
        }
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    Size size = MediaQuery.of(context).size;
    double lcdX = size.width / 375;

    Widget? prefix;
    if (widget.prefix != null) {
      prefix = widget.prefix!;
    } else if (widget.prefixIcon != null) {
      prefix = IconTheme(
          data: IconThemeData(
            color: widget.prefixIconColor,
          ),
          child: widget.prefixIcon!);
    } else if (widget.prefixText?.isNotEmpty ?? false) {
      prefix = Text(
        widget.prefixText ?? '',
        style: widget.prefixStyle ??
            TextStyle(
              fontSize: kTextFieldFontSize * lcdX,
              color: const Color(0xFF666666),
            ),
      );
    }
    if (widget.prefixContainObscureText && prefix != null) {
      prefix = Row(
        children: [
          Text(
            '*',
            style: TextStyle(
                fontSize: kTextFieldFontSize * lcdX,
                color: Colors.red,
                height: 1.3),
          ),
          prefix,
        ],
      );
    }

    ///maxLines超过1行的时候，不需要清除按钮
    Widget? cleanButtonSuffix;
    if (widget.showCleanButton == true &&
        widget.readOnly == false &&
        _isShowCleanButton == true &&
        _controller.text.isNotEmpty &&
        (widget.maxLines == null ||
            (widget.maxLines != null && widget.maxLines! == 1))) {
      //默认有padding大小，最小BoxConstraints约束
      cleanButtonSuffix = IconButton(
          constraints: const BoxConstraints(minWidth: 0),
          padding: EdgeInsets.all(_iconPaddingSize),
          iconSize: 20,
          onPressed: () {
            _controller.clear();
            setState(() {
              _isShowCleanButton = false;
            });
            if (widget.onChanged != null) {
              widget.onChanged!('');
            }
          },
          icon: const Icon(
            Icons.clear_rounded,
          ));
    }

    ///maxLines超过1行的时候，不需要密码眼睛按钮
    Widget? obscureTextEyeWidget;
    if (widget.showObscureTextEye == true &&
        (widget.maxLines == null ||
            (widget.maxLines != null && widget.maxLines! == 1))) {
      obscureTextEyeWidget = IconButton(
        constraints: const BoxConstraints(minWidth: 0),
        padding: EdgeInsets.all(_iconPaddingSize),
        iconSize: 20,
        icon: Image.asset('assets/images/password_eye.png',
            fit: BoxFit.contain,
            width: 20,
            height: 20,
            color: null,
            errorBuilder: (
                BuildContext context,
                Object error,
                StackTrace? stackTrace,
                ) =>
            const Icon(
              Icons.error,
            )),
        onPressed: () {
          setState(() {
            _isShowObscureText = !_isShowObscureText;
          });
        },
      );
    }

    Widget? suffix;
    if (widget.suffix != null) {
      suffix = widget.suffix;
    }

    ///是否是多行文本
    bool multiline =
    (widget.maxLines != null && widget.maxLines! > 1) ? true : false;

    Widget textFieldMaterialWidget;
    textFieldMaterialWidget = TextField(
      controller: _controller,
      focusNode: widget.readOnly ? null : _focusNode,
      autofocus: widget.autofocus,
      decoration: InputDecoration(
        isCollapsed: multiline ? false : true,
        border: InputBorder.none,
        // border:OutlineInputBorder(),
        hintText: widget.hintText ?? "请点击输入",
        hintStyle: widget.hintStyle ??
            TextStyle(
              fontSize: kTextFieldFontSize * lcdX,
              color: const Color(0xFFCCCCCC),
            ),
      ),
      keyboardType: widget.keyboardType,
      textInputAction: widget.textInputAction,
      style: widget.style ??
          TextStyle(
            fontSize: kTextFieldFontSize * lcdX,
            color: const Color(0xFF333333),
          ),
      readOnly: widget.readOnly,
      obscureText: _isShowObscureText,
      maxLines: widget.maxLines,
      maxLength: widget.maxLength,
      inputFormatters: widget.inputFormatters,
      onTap: () {
        if (widget.onTap != null) {
          widget.onTap!();
        }
        if (widget.onTapBlockController != null) {
          widget.onTapBlockController!(_controller);
        }
      },
      onChanged: (value) {
        if (widget.showCleanButton && widget.readOnly == false) {
          setState(() {
            _isShowCleanButton = value.isNotEmpty;
          });
        }
        if (widget.onChanged != null) {
          widget.onChanged!(value);
        }
      },
      onSubmitted: widget.onSubmitted,
      // 注意：1）系统函数默认会关闭键盘，如果自己实现这个回调，则需要自己关闭键盘。2）点击屏幕关闭第一响应者的不会回调这个函数。
      // onEditingComplete: (){
      //   // _focusNode.unfocus();
      // },
    );

    EdgeInsetsGeometry textFieldPadding = (prefix == null
        ? EdgeInsets.zero
        : EdgeInsets.only(left: widget.gapPrefixSpace ?? 8));

    final primaryColor = Theme.of(context).primaryColor;
    BoxBorder? border;
    if (_focused == true) {
      border = widget.focusedBorder ??
          Border.all(
            color: primaryColor,
            width: 1,
          );
    } else {
      border = widget.border;
    }

    ///多行文本的时候，自己设置不同的padding（containerPadding），系统textField的inputDecoration有自带padding。
    EdgeInsetsGeometry padding;
    if (widget.containerPadding != null) {
      padding = widget.containerPadding!;
    } else {
      padding = multiline
          ? const EdgeInsets.fromLTRB(16, 0, 16, 12)
          : const EdgeInsets.symmetric(horizontal: 16, vertical: 0);
    }
    EdgeInsetsGeometry focusedPadding = EdgeInsets.symmetric(
        horizontal: _focused ? 0 : border?.top.width ?? 1,
        vertical: _focused ? 0 : border?.top.width ?? 1);
    EdgeInsetsGeometry newPadding = padding.add(focusedPadding);
    return AnimatedContainer(
      ///高度:多行文本的时候，让自适应高度
      height: multiline ? null : _height,
      width: widget.width,
      margin: widget.containerMargin,
      decoration: BoxDecoration(
          color: widget.containerBackgroundColor ?? const Color(0xFFF2F1F6),
          borderRadius: widget.borderRadius ?? BorderRadius.circular(5),
          //默认
          shape: BoxShape.rectangle,
          border: border),
      padding: newPadding,
      duration: const Duration(milliseconds: 200),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          if (prefix != null) prefix,
          Expanded(
            child: Padding(
              padding: textFieldPadding,
              child: textFieldMaterialWidget,
            ),
          ),
          if (cleanButtonSuffix != null) cleanButtonSuffix,
          if (obscureTextEyeWidget != null) obscureTextEyeWidget,
          if (suffix != null) suffix,
        ],
      ),
    );
  }
}
